#pragma once

#include "Intaddr.h"
#include "Common.h"
#include "Log.h"
#include <memory>
#include <string>

namespace zmh
{
    // 模板设计类
    // Tcp套接字 Udp套接字 listen套接字 accept套接字 共用一个基类
    // 目前只涉及Tcp套接字的封装
    class Socket
    {
    public:
        virtual void socket() = 0;
        virtual void bind(const Intaddr &) = 0;
        virtual void listen(int) = 0;
        virtual void connect(const Intaddr &) = 0;
        virtual std::shared_ptr<Socket> accept(Intaddr &) = 0;
        virtual ssize_t recv(std::string &) = 0;
        virtual ssize_t send(std::string &) = 0;
        virtual void close() = 0;
        virtual ~Socket() {} // 为什么父类必须要有定义？？？

        void GetListenSocket(const Intaddr &_add, int backlog)
        {
            socket();
            bind(_add);
            listen(backlog);
        }
        void GetConnectSocket(const Intaddr &_add)
        {
            socket();
            connect(_add);
        }
    };

    class Tcp_Socket : public Socket
    {
    public:
        void socket() override
        {
            int n = ::socket(AF_INET, SOCK_STREAM, 0);
            if (_socketfd = n, n == -1)
            {
                LOG(LogLevel::FATAL) << "socket mistake: " << strerror(errno);
                exit(Err_Socket);
            }
            LOG(LogLevel::INFO) << "socket sucess";
        }
        void bind(const Intaddr &addr) override
        {
            int n = ::bind(_socketfd, (const struct sockaddr *)&addr.Addr(), sizeof addr.Addr());
            if (n == -1)
            {
                LOG(LogLevel::FATAL) << "bind mistake: " << strerror(errno);
                exit(Err_Bind);
            }
            LOG(LogLevel::INFO) << "bind sucess";
        }
        void listen(int backlog) override
        {
            int n = ::listen(_socketfd, 8);
            if (n == -1)
            {
                LOG(LogLevel::FATAL) << "listen mistake: " << strerror(errno);
                exit(Err_Listen);
            }
            LOG(LogLevel::INFO) << "listen sucess";
        }
        void connect(const Intaddr &addr) override
        {
            int n = ::connect(_socketfd, (const struct sockaddr *)&addr.Addr(), sizeof addr.Addr());
            if (n == -1)
            {
                LOG(LogLevel::FATAL) << "connect mistake: " << strerror(errno);
                exit(Err_Connect);
            }
            LOG(LogLevel::INFO) << "connect sucess";
        }
        std::shared_ptr<Socket> accept(Intaddr &peer) override
        {
            struct sockaddr_in basic_peer;
            socklen_t len = sizeof basic_peer;
            int n = ::accept(_socketfd, (struct sockaddr *)&basic_peer, &len);
            if (n == -1)
            {
                // 接受发生错误没必要直接退出服务器,返回一个空指针
                LOG(LogLevel::WARNING) << "accept mistake: " << strerror(errno);
                return nullptr;
            }
            peer = Intaddr(basic_peer);
            LOG(LogLevel::INFO) << "accept sucess: " << peer.Name();
            return std::make_shared<Tcp_Socket>(n); // 僵亡值
        }
        ssize_t recv(std::string &out) override
        {
            char buffer[1024] = {0};
            ssize_t n = ::recv(_socketfd, buffer, sizeof(buffer) - 1, 0);
            if (n == -1)
                LOG(LogLevel::WARNING) << "recv mistake: " << strerror(errno);
            else if (n == 0)
                LOG(LogLevel::INFO) << "recv eof";
            else
                out += buffer;
            return n;
        }
        ssize_t send(std::string &in) override
        {
            ssize_t n = ::send(_socketfd, in.c_str(), in.size(), 0);
            if (n == -1)
                LOG(LogLevel::WARNING) << "send mistake: " << strerror(errno);
            return n;
        }
        void close() override
        {
            if (_socketfd == -1)
                return;

            ::close(_socketfd);
            _socketfd = -1;
        }
        virtual ~Tcp_Socket() override
        {
            close();
        }

        Tcp_Socket(int socketfd = -1) : _socketfd(socketfd) {}

    private:
        int _socketfd;
    };
}